package w83a.w83aUtilidadesComunes;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.xpath.XPathAPI;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import com.izenpe.miscelanea.Miscelanea;
import java.io.ByteArrayInputStream;

public class W83aDOMUtils implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Constructor vacio
	 */
	private W83aDOMUtils() {
		
	}
	
	/**
	 * Crea un objeto de la clase Document a partir del String XML que recibe como parámetro
	 * @param xml String que contiene el XML
	 * @return Document Objeto de la clase Document
	 * @throws W83aDOMUtilException excepcion
	 */
	public static Document createDoc(String xml) throws W83aDOMUtilException {
		try{
			return(W83aDOMUtils.buildDocument(xml, "8859_1"));
		}catch(UnsupportedEncodingException e){
			throw new W83aDOMUtilException("Error createDoc", e);
		}catch(SAXException e){
			throw new W83aDOMUtilException("Error createDoc", e);
		}catch(IOException e){
			throw new W83aDOMUtilException("Error createDoc", e);
		}catch(ParserConfigurationException e){
			throw new W83aDOMUtilException("Error createDoc", e);
		}
	}
	
	/**
	 * Crea un objeto de la clase Document a partir del String XML que recibe como parámetro
	 * @param xml String que contiene el XML UTF-8
	 * @return Document Objeto de la clase Document
	 * @throws W83aDOMUtilException excepcion
	 */
	public static Document createNSDocUTF8(String xml) throws W83aDOMUtilException {
		try{
			return(W83aDOMUtils.buildNSDocument(xml,"UTF-8"));
		}catch(UnsupportedEncodingException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}catch(SAXException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}catch(IOException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}catch(ParserConfigurationException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}
	}
	
	/**
	 * Crea un objeto document a partir de XML con la codificación enviada
	 * @param strPfXML String con el XML
	 * @param strPfEnconding Codificacion enviada
	 * @return Document Objeto de la clase Document
	 * @throws ParserConfigurationException excepcion de parseo
	 * @throws UnsupportedEncodingException encode
	 * @throws IOException IO
	 * @throws SAXException error de sax
	 */	
	private static Document buildNSDocument(String strPfXML, String strPfEnconding) throws ParserConfigurationException, UnsupportedEncodingException, IOException, SAXException {
		Document doc = null;
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		documentBuilderFactory.setNamespaceAware(true);
		DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
		doc = documentBuilder.parse((InputStream) new ByteArrayInputStream(strPfXML.getBytes(strPfEnconding)));
		return(doc);	
	}
	
	/**
	 * Recupera la lista de nodos de una etiqueta  
	 * @param document El documento del que se recupera la lista de nodos
	 * @param elementUrl Ruta de la etiqueta dentro del documento, separado por '.'
	 * @return NodeList Lista de nodos
	 * @throws Exception
	 */	
	public static NodeList retrieveElementsListNS(Document document, String elementUrl) {
		NodeList listaResultado = null;
		StringTokenizer tokCadena = null;
		String bloque = null;
		NodeList listaNodos = null;
		Element elemento = null;
		int contador = 0;
			tokCadena = new StringTokenizer(elementUrl, ".");
			while (tokCadena.hasMoreTokens()) {
				bloque = tokCadena.nextToken();
				if (contador == 0) {
					listaNodos = document.getElementsByTagNameNS("*",bloque);
				} else {
					listaNodos = elemento.getElementsByTagNameNS("*",bloque);
				}
				elemento = (Element)listaNodos.item(0);
				if(elemento==null){
					return null;
				}
				contador++;
			}
			if (listaNodos != null) {
				listaResultado = listaNodos;
			}
			return(listaResultado);
	}

	/**
	 * Crea un objeto de la clase Document a partir del String XML que recibe como parámetro
	 * @param xml String que contiene el XML UTF-8
	 * @return Document Objeto de la clase Document
	 * @throws W83aDOMUtilException excepcion
	 */
	public static Document createDocUTF8(String xml) throws W83aDOMUtilException {
		try{
			return(W83aDOMUtils.buildDocument(xml,"UTF-8"));
		}catch(UnsupportedEncodingException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}catch(SAXException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}catch(IOException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}catch(ParserConfigurationException e){
			throw new W83aDOMUtilException("Error createDocUTF8", e);
		}
	}

	/**
	 * Crea un objeto document a partir de XML con la codificación enviada
	 * @param strPfXML String con el XML
	 * @param strPfEnconding Codificacion enviada
	 * @return Document Objeto de la clase Document
	 * @throws ParserConfigurationException excepcion de parseo
	 * @throws UnsupportedEncodingException encode
	 * @throws IOException IO
	 * @throws SAXException error de sax
	 */	
	private static Document buildDocument(String strPfXML, String strPfEnconding) throws ParserConfigurationException, UnsupportedEncodingException, IOException, SAXException {
		Document doc = null;
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
		doc = documentBuilder.parse((InputStream) new ByteArrayInputStream(strPfXML.getBytes(strPfEnconding)));
		return(doc);	
	}

	/**
	 * Recupera el valor de un elemento    
	 * @param elemento Elemento del que se recuperar el valor
	 * @return String Valor del elemento
	 * @throws
	 */	
	public static String retrieveElementValue(Element elemento)  {
		return(elemento.getChildNodes().item(0).getNodeValue());
	}

	/**
	 * Recupera el valor del nodo de un elemento
	 * @param nodo Nodo del que se recuperar el valor
	 * @return String Valor del nodo
	 * @throws
	 */
	public static String retrieveNodeValue(Node nodo) {
		return nodo.getChildNodes().item(0).getNodeValue();
	
	}

	/**
	 * Recupera el valor de una etiqueta (nodo) de un documento
	 * @param document Documento del que se recupera el valor de la etiqueta
	 * @param elementUrl Ruta del nodo dentro del documento, separado por '.'
	 * @return String Valor de la etiqueta (nodo)
	 * @throws Exception
	 */
	public static String retrieveElementValue(Document document, String elementUrl) {
		String result = null;
		StringTokenizer strTok = null;
		String block = null;
		NodeList nodeList = null;
		Element element = null;
		int cont = 0;
			strTok = new StringTokenizer(elementUrl, ".");
			while (strTok.hasMoreTokens()) {
				block = strTok.nextToken();
				if (cont == 0) {
					nodeList = document.getElementsByTagName(block);
				} else {
					nodeList = element.getElementsByTagName(block);
				}
				element = (Element)nodeList.item(0);
				if(element==null) {
					return null;
				}
				cont++;
			}	
			if (nodeList.item(0).getChildNodes().item(0) != null) {
				result = (nodeList.item(0).getChildNodes().item(0)).getNodeValue();
			}			
			return(result);
	}

	/**
	 * Recupera la lista de nodos de una etiqueta  
	 * @param document El documento del que se recupera la lista de nodos
	 * @param elementUrl Ruta de la etiqueta dentro del documento, separado por '.'
	 * @return NodeList Lista de nodos
	 * @throws Exception
	 */	
	public static NodeList retrieveElementsList(Document document, String elementUrl) {
		NodeList listaResultado = null;
		StringTokenizer tokCadena = null;
		String bloque = null;
		NodeList listaNodos = null;
		Element elemento = null;
		int contador = 0;
			tokCadena = new StringTokenizer(elementUrl, ".");
			while (tokCadena.hasMoreTokens()) {
				bloque = tokCadena.nextToken();
				if (contador == 0) {
					listaNodos = document.getElementsByTagName(bloque);
				} else {
					listaNodos = elemento.getElementsByTagName(bloque);
				}
				elemento = (Element)listaNodos.item(0);
				if(elemento==null){
					return null;
				}
				contador++;
			}
			if (listaNodos != null) {
				listaResultado = listaNodos;
			}
			return(listaResultado);
	}

	/**
	 * Devuelve un nodo de una lista de nodos
	 * @param nodelist Lista de nodos
	 * @param element Entero con el elemento de la lista de nodos
	 * @param node nodo
	 * @return nodo El nodo dentro del elemento
	 * @throws Exception
	 */
	public static Node retrieveNode(NodeList nodelist, int element, int node){
		return nodelist.item(element).getChildNodes().item(node);
	}

	/**
	 * Devuelve el valor de un atributo
	 * @param node Nodo que contiene el atributo
	 * @param strPfAttribute String con el nombre del atributo
	 * @return String Valor del atributo
	 * @throws Exception
	 */
	public static String attributeValue(Node node, String strPfAttribute){
		if(node.hasAttributes()) {
			return node.getAttributes().getNamedItem(strPfAttribute).getNodeValue();
		} else {
    		return("");
		}
	}

	/**
	 * Devuelve el String a partir de un Document
	 * @param doc Documento que contiene el XML
	 * @return String que contiene el XML
	 * @throws W83aDOMUtilException excepcion DOM
	 */
	public static String getStringDoc(Document doc) throws W83aDOMUtilException {
		try{
			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			transformer.transform(source, result);
			String xmlString = sw.toString();
			return(xmlString);
		}catch(TransformerConfigurationException e){
			throw new W83aDOMUtilException("Error getStringDoc", e);
		}catch(TransformerException e){
			throw new W83aDOMUtilException("Error getStringDoc", e);
		}
		
		
	}

	/**
	 * Parsea un documento XML
	 * @param xml String que contiene el XML
	 * @param s String
	 * @return String Documento XML parseado
	 * @throws W83aDOMUtilException excepcion DOM
	 */
	public static String parserXML(String xml, String s) throws W83aDOMUtilException {
		int i = xml.indexOf("<" + s + ">");
		if (i==-1){
			return "NOEXISTE";
		}
		String salida = xml.substring(i + s.length() + 2, xml.indexOf("</" + s + ">"));
		return(salida);
	}

	/**
	 * Recupera el vector con los valores de los elementos parseados en un documento XML 
	 * @param strPfDocument Documento que contiene el XML
	 * @param strPfXPath Ruta de la etiqueta que se quiere parsear
	 * @return Vector con todas las etiquetas parseadas
	 * @throws W83aDOMUtilException excepcion
	 */
	public static ArrayList retrieveVectorElementsValueXPath(Document strPfDocument, String strPfXPath) throws W83aDOMUtilException {
		try{
			NodeList nodeLi;
			nodeLi = XPathAPI.selectNodeList(strPfDocument, strPfXPath);
			ArrayList arrayElementos = new ArrayList();
			for (int i = 0; i < nodeLi.getLength(); ++i) {
				if (!nodeLi.item(i).getNodeValue().equals("")) {
					arrayElementos.add(nodeLi.item(i).getNodeValue());
				}
			}
			return(arrayElementos);
		}catch(TransformerException e){
			throw new W83aDOMUtilException("Error retrieveVectorElementsValueXPath", e);
		}
	}
	
    /**
     * Codifica en Base64 un array de bytes      
     * @param decData Datos a codificar                
     * @return String con los datos codificados en Base64
     * @throws
     */   
    public static String encode(byte[] decData) {
       String encData;
       BASE64Encoder encoder;
       encoder = new BASE64Encoder();
       encData = encoder.encode(decData);
       return encData;
    }//Fin encode	
    
	/**
	 * Decodifica una cadena de caracteres que se encuentra codificada en formato Base64
	 * @param encData Datos a decodificar
	 * @return Bytes con los datos decodificados
	 * @throws W83aDOMUtilException excepcion DOM
	 */  
	public static byte[] decode(String encData) throws W83aDOMUtilException{
		try{
			byte[] decData;
			BASE64Decoder decoder;
			decoder = new BASE64Decoder();
			decData = decoder.decodeBuffer(encData);
			return decData;
		}catch(IOException e){
			throw new W83aDOMUtilException("Error decode", e);
		}
	}

	/**
	 * Decodifica una cadena de caracteres que se encuentra codificada en formato Base64
	 * @param encData Datos a decodificar
	 * @return String con los datos decodificados
	 * @throws W83aDOMUtilException excepcion DOM
	 */
	public static String decodeToString(String encData) throws W83aDOMUtilException {
		String decoded = null;
		decoded = new String(W83aDOMUtils.decode(encData));
		return decoded;
	}
	
	/**
	 * Método que aplica la construcción CDATA (Character Data) a los datos pasados como parámetro
	 * para informar al procesador de XML de que no hay marcado en los caracteres que contiene la sección.
	 * @param data Datos a meter en la estructura CDATA.
	 * @return String
	 */
	public static String xmlData(String data){
		return "<![CDATA[" + data + "]]>";
	}
	
	/**
	 * Codifica en Base64 una cadena de caracteres
	 * 
	 * @param decData
	 *            Datos a codificar
	 * @return String con los datos codificados en Base64
	 * @throws
	 */
	public static String encodeString(String decData) {
		return W83aDOMUtils.encode(decData.getBytes());
	}

	/**
	 * Retorna el string que se le pasa como parámetro en b64
	 * 
	 * @param firma
	 *            String. Texto a pasar a b64
	 * @return byte[]: Array de bytes con el texto en base 64
	 * @throws Exception
	 */
	public static byte[] fncObtenerBytes64(String firma) {

		try {

			Miscelanea m = new Miscelanea();

			ByteArrayOutputStream firmaBos = new ByteArrayOutputStream();
			m.base64Bin(firma, firmaBos);
			byte[] byteFirma = firmaBos.toByteArray();
			byteFirma.toString();
			return byteFirma;

		} catch (Throwable e) {
			// System.out.println(e);
			return null;
		}

	}
	
	
	/**
	 * Codifica en Base64 una cadena de caracteres utilizando el encodig pasado
	 * como parametro
	 * 
	 * @param decData
	 *            Datos a codificar
	 * @param enc
	 *            encoding para codificar
	 * @return String con los datos codificados en Base64
	 * @throws
	 */
	public static String encodeString(String decData, String enc) {

		try {
			byte[] bytes = decData.getBytes(enc);
			return W83aDOMUtils.encode(bytes);
		} catch (UnsupportedEncodingException e) {
			// e.printStackTrace();

			return null;
		}
	}
	
	/**
	 * quita los \n que vienen en los XML de Dokusi e interoperabilidad
	 * 
	 * @param xml
	 *            : xml de entrada
	 * @return xml : xml de salida
	 */
	public static String parsearStringDokusi(String xml) {

		StringTokenizer TokenizerXML = new StringTokenizer(xml, "\n");

		StringBuffer Sbxml = new StringBuffer();
		// String Sbxml2 = new String();
		String Sbxml2 = "";

		while (TokenizerXML.hasMoreElements()) {
			Sbxml2 = TokenizerXML.nextToken();
			Sbxml.append(Sbxml2.trim());
		}
		xml = Sbxml.toString();

		return xml;

	}
	
}
